package com.itsu.job.config;

import cn.hutool.core.collection.CollUtil;
import com.itsu.job.components.RpcRegister;
import com.itsu.job.context.JobHelper;
import com.itsu.job.controller.JobController;
import com.itsu.job.rpc.JobRpcServerExecutor;
import com.itsu.job.rpc.JobRpcServerHandler;
import com.itsu.job.rpc.basic.EmbedHttpServerHandler;
import com.itsu.job.rpc.request.SearchJobRequestHandler;
import com.itsu.job.rpc.request.ListJobRequestHandler;
import com.itsu.job.rpc.request.RpcRegisterRequestHandler;
import com.itsu.job.rpc.request.RunJobRequestHandler;
import com.itsu.job.rpc.request.ScheduleJobRequestHandler;
import com.itsu.job.rpc.request.StatusJobRequestHandler;
import com.itsu.job.rpc.request.StopJobRequestHandler;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.task.TaskSchedulerBuilder;
import org.springframework.boot.task.TaskSchedulerCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;

import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * @author Jerry.Su
 * @Date 2021/12/31 10:43
 */
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
@EnableConfigurationProperties(value = {JobProperties.class})
@EnableScheduling
@ConditionalOnProperty(name = "itsu.job.enable", havingValue = "true")
public class JobAutoConfiguration {

    private final JobProperties jobProperties;

    public JobAutoConfiguration(JobProperties jobProperties) {
        this.jobProperties = jobProperties;
    }

    @Bean
    public JobHelper jobHelper(ThreadPoolTaskScheduler threadPoolTaskScheduler) {
        return new JobHelper(threadPoolTaskScheduler, jobProperties);
    }

    @Bean
    public ApplicationRunner jobApplicationRunner(JobHelper jobHelper) {
        return new JobApplicationRunner(jobProperties, jobHelper);
    }

    @Bean
    public TaskSchedulerBuilder taskSchedulerBuilder(ObjectProvider<TaskSchedulerCustomizer> taskSchedulerCustomizers) {
        TaskSchedulerBuilder builder = new TaskSchedulerBuilder();
        builder = builder.poolSize(jobProperties.getPool().getSize());
        JobProperties.Shutdown shutdown = jobProperties.getShutdown();
        builder = builder.awaitTermination(shutdown.isAwaitTermination());
        builder = builder.awaitTerminationPeriod(shutdown.getAwaitTerminationPeriod());
        builder = builder.threadNamePrefix(jobProperties.getThreadNamePrefix());
        builder = builder.customizers(taskSchedulerCustomizers);
        return builder;
    }

    @Bean
    public ThreadPoolTaskScheduler taskScheduler(TaskSchedulerBuilder taskSchedulerBuilder) {
        return taskSchedulerBuilder.build();
    }

    @Bean
    public RpcRegister rpcRegister() {
        return new RpcRegister(jobProperties);
    }


    public static class JobApplicationRunner implements ApplicationRunner {

        private final JobProperties jobProperties;

        private final JobHelper jobHelper;

        public JobApplicationRunner(JobProperties jobProperties, JobHelper jobHelper) {
            this.jobProperties = jobProperties;
            this.jobHelper = jobHelper;
        }

        @Override
        public void run(ApplicationArguments args) throws Exception {
            if (jobProperties.isEnable() && jobProperties.isAutoSchedule()) {
                List<Job> jobs = jobProperties.getJobs();
                if (CollUtil.isNotEmpty(jobs)) {
                    for (Job job : jobs) {
                        jobHelper.scheduleJob(job);
                    }
                }
            }
        }
    }

//    @Bean
//    public JobRpcConfig jobRpcConfig(JobHelper jobHelper) {
//        return new JobRpcConfig(this.jobProperties, jobHelper);
//    }

    @Configuration
    public static class JobRpcConfig {

        JobProperties jobProperties;

        JobHelper jobHelper;

        public JobRpcConfig(JobProperties jobProperties, JobHelper jobHelper) {
            this.jobProperties = jobProperties;
            this.jobHelper = jobHelper;
        }

        @Bean
        public JobController jobController() {
            return new JobController();
        }

        @Bean
        public JobRpcServerExecutor jobRpcServerExecutor(EmbedHttpServerHandler embedHttpServerHandler) {
            return new JobRpcServerExecutor(jobProperties, embedHttpServerHandler);
        }

        @Bean
        public EmbedHttpServerHandler jobRpcRequestHandler(RunJobRequestHandler runJobRequestHandler,
                                                           ScheduleJobRequestHandler scheduleJobRequestHandler,
                                                           StopJobRequestHandler stopJobRequestHandler,
                                                           StatusJobRequestHandler statusJobRequestHandler,
                                                           SearchJobRequestHandler allStatusJobRequestHandler,
                                                           RpcRegisterRequestHandler rpcRegisterRequestHandler,
                                                           ListJobRequestHandler listJobRequestHandler) {
            JobProperties.Rpc rpc = jobProperties.getRpc();
            String accessToken = rpc.getAccessToken();
            ThreadPoolExecutor bizThreadPool = new ThreadPoolExecutor(
                    rpc.getCoreSize(),
                    rpc.getMaxPoolSize(),
                    rpc.getKeepAliveTime(),
                    TimeUnit.SECONDS,
                    new LinkedBlockingQueue<>(rpc.getWorkQueueSize()),
                    r -> new Thread(r, " EmbedServer bizThreadPool-" + r.hashCode()),
                    (r, executor) -> {
                        throw new RuntimeException(" EmbedServer bizThreadPool is EXHAUSTED!");
                    });
            return new JobRpcServerHandler(accessToken, bizThreadPool, stopJobRequestHandler, scheduleJobRequestHandler,
                    runJobRequestHandler, statusJobRequestHandler, allStatusJobRequestHandler, rpcRegisterRequestHandler,
                    listJobRequestHandler);
        }

        @Bean
        @ConditionalOnMissingBean(RunJobRequestHandler.class)
        public RunJobRequestHandler runJobRequestHandler() {
            return new RunJobRequestHandler(jobHelper);
        }

        @Bean
        @ConditionalOnMissingBean(ScheduleJobRequestHandler.class)
        public ScheduleJobRequestHandler scheduleJobRequestHandler() {
            return new ScheduleJobRequestHandler(jobHelper);
        }

        @Bean
        @ConditionalOnMissingBean(StopJobRequestHandler.class)
        public StopJobRequestHandler stopJobRequestHandler() {
            return new StopJobRequestHandler(jobHelper);
        }

        @Bean
        @ConditionalOnMissingBean(StatusJobRequestHandler.class)
        public StatusJobRequestHandler statusJobRequestHandler() {
            return new StatusJobRequestHandler(jobHelper);
        }

        @Bean
        @ConditionalOnMissingBean(SearchJobRequestHandler.class)
        public SearchJobRequestHandler allStatusJobRequestHandler() {
            return new SearchJobRequestHandler(jobHelper, jobProperties);
        }

        @Bean
        @ConditionalOnMissingBean(RpcRegisterRequestHandler.class)
        public RpcRegisterRequestHandler rpcRegisterRequestHandler() {
            return new RpcRegisterRequestHandler();
        }

        @Bean
        @ConditionalOnMissingBean(ListJobRequestHandler.class)
        public ListJobRequestHandler listJobRequestHandler() {
            return new ListJobRequestHandler(jobProperties);
        }
    }
}
